#![allow(dead_code)]
//! 过程宏的用法
//! 1. [cached]
//!
//! 缓存类型(需实现[Cached] 或 [CachedAsync] trait)
//! # 属性
//! - name: (可选，字符串) 指定生成的缓存名称，默认为函数名称大写。
//! - size: (可选，usize) 指定 LRU 最大大小，意味着缓存类型是 SizedCache 或 TimedSizedCache。
//! - time: (可选，u64) 指定缓存 TTL（生存时间）为秒，意味着缓存类型是 TimedCache 或 TimedSizedCache。
//! - time_refresh: (可选，布尔值) 指定是否在缓存命中时刷新 TTL。
//! - sync_writes: (可选，字符串) 指定是否同步执行和写入未缓存的值。
//!  未指定时，将采用 "default" 行为，同步所有键，在未缓存的执行期间锁定整个缓存。
//!  当设置为 "by_key" 时，使用每个键的锁来同步重复键的未缓存执行。
//! - ty: (可选，字符串类型) 要使用的缓存存储类型。默认为 UnboundCache。
//!   当指定 unbound 时，默认为 UnboundCache。
//!   当指定 size 时，默认为 SizedCache。
//!   当指定 time 时，默认为 TimedCached。
//!   当同时指定 size 和 time 时，默认为 TimedSizedCache。
//!   当指定 ty 时，必须也指定 create。
//! - create: (可选，字符串表达式) 指定用于创建新缓存存储的表达式，
//!   例如 create = r##"{ CacheType::new() }"## 。
//! - key: (可选，字符串类型)指定用于缓存键的类型，
//!   例如 key = "u32"。当指定 key 时，必须也指定 convert。
//! - convert: (可选，字符串表达式)指定用于将函数参数转换为缓存键的表达式，
//!   例如 convert = r##"{ format!("{}:{}", arg1, arg2) }"## 。
//!   当指定 convert 时， key 或 ty 也必须设置。
//! - result: (可选，布尔值)如果你的函数返回一个 Result，则只缓存函数返回的 Ok 值。
//! - option: (可选，布尔值) 如果你的函数返回一个 Option，则只缓存函数返回的 Some 值。
//! - with_cached_flag: (可选，布尔值) 如果你的函数返回一个 cached::Return 或 Result<cached::Return, E>，
//!   当返回一个缓存值时，cached::Return.was_cached 标志将被更新。
//! - result_fallback: (可选，布尔值) 如果你的函数返回一个 Result 并且失败， 缓存将刷新最近过期的 Ok 值。
//!   换句话说，刷新是尽力而为的 - 返回 Ok 按常规刷新，但 Err 将回退到最后一个 Ok。
//!   这在例如保持网络操作在断开网络连接期间的最后成功结果时非常有用。
//!   注意 ，此选项要求缓存类型实现 CloneCached。
//! 2. [io_cached]
//!
//! 缓存类型(需实现[IOCached] 或 [IOCachedAsync] trait)
//! # 属性
//! - name: (可选，字符串)指定生成的缓存名称，默认为函数名称的大写。
//! - size: (可选，usize)指定 LRU 最大大小，意味着缓存类型为 SizedCache 或 TimedSizedCache。
//! - time: (可选，u64)指定缓存 TTL（生存时间）以秒为单位，意味着缓存类型为 TimedCache 或 TimedSizedCache。
//! - time_refresh: (可选，布尔值) 指定是否在缓存命中时刷新 TTL。
//! - sync_writes: (可选，字符串) 指定是否同步执行和写入未缓存的值。
//!   未指定时，将采用 "default" 行为，同步所有键，在未缓存的执行期间锁定整个缓存。
//!   当设置为 "by_key" 时，使用每个键的锁来同步重复键的未缓存执行。
//! - ty: (可选，字符串类型) 要使用的缓存存储类型。 默认为 UnboundCache。
//!   当指定 unbound 时，默认为 UnboundCache。
//!   当指定 size 时，默认为 SizedCache。
//!   当指定 time 时，默认为 TimedCached。
//!   当同时指定 size 和 time 时，默认为 TimedSizedCache。
//!   当指定 ty 时，必须也指定 create。
//! - create: (可选，字符串表达式) 指定用于创建新缓存存储的表达式，
//!   例如 create = r##"{ CacheType::new() }"## 。
//! - key: (可选，字符串类型)指定用于缓存键的类型，例如 key = "u32"。
//!   当指定 key 时，必须也指定 convert。
//! - convert: (可选，字符串表达式)指定用于将函数参数转换为缓存键的表达式，
//!   例如 convert = r##"{ format!("{}:{}", arg1, arg2) }"## 。
//!   当指定 convert 时， key 或 ty 也必须设置。
//! - result: (可选，布尔值)如果你的函数返回一个 Result，则只缓存函数返回的 Ok 值。
//! - option: (可选，布尔值) 如果你的函数返回一个 Option，则只缓存函数返回的 Some 值。
//! - with_cached_flag: (可选，布尔值) 如果你的函数返回一个 cached::Return 或 Result<cached::Return, E>，
//!   当返回一个缓存值时，cached::Return.was_cached 标志将被更新。
//! - result_fallback: (可选，布尔值) 如果你的函数返回一个 Result 并且失败，缓存将刷新最近过期的 Ok 值。
//!   换句话说，刷新是尽力而为的 - 返回 Ok 按常规刷新，但 Err 将回退到最后一个 Ok。
//!   这在例如保持网络操作在断开网络连接期间的最后成功结果时非常有用。
//!   注意 ，此选项要求缓存类型实现 CloneCached。
//!
//! > ty、create、key 和 convert 属性必须是String，
//! > 这是因为用于解析属性的 darling 不支持直接将属性解析为 Types 或 Blocks。
use cached::proc_macro::{cached, io_cached, once};
use cached::AsyncRedisCache;
use cached::SizedCache;
use std::thread::sleep;
use std::time::Duration;
use dotenvy::dotenv;
use thiserror::Error;

#[tokio::main]
async fn main() {
    dotenv().ok();
    let r = async_cached_sleep_secs(2).await.unwrap();
    println!("sleep_secs: {}", r);

    tokio::time::sleep(Duration::from_secs(10)).await;
}

/// 定义一个名为`fib`的函数，该函数使用隐式命名为 FIB的缓存。
/// 默认情况下，缓存将是函数的名称，全部大写。
/// 以下行等效于 #[cached（name = "FIB"， unbound）]
#[cached]
fn fib(n: u64) -> u64 {
    println!("n的值为：{n}");
    n
}

/// 将显式缓存类型与自定义创建块和自定义缓存键生成块一起使用
///
/// - ty: 指定缓存类型(需实现[Cached] 或 [CachedAsync] trait)
/// - create: 创建缓存对象
/// - convert： 指定缓存的key
#[cached(
    ty = "SizedCache<String, usize>",
    create = "{ SizedCache::with_size(100) }",
    convert = r#"{ format!("{}{}", a, b) }"#
)]
fn cached_keyed(a: &str, b: &str) -> usize {
    let size = a.len() + b.len();
    sleep(Duration::from_secs(size as u64));
    size
}

/// 仅缓存初始函数调用。
/// 函数将在缓存过期后重新执行（以秒为单位 time ）。
/// 当没有（或过期）缓存时，并发调用将同步 （sync_writes），
/// 因此该函数只执行一次。
/// >`sync_writes` 和 `result_fallback` 不能一起使用
#[once(time=10, option = true, sync_writes = true)]
fn once_keyed(a: String) -> Option<usize> {
    if a == "a" {
        Some(a.len())
    } else {
        None
    }
}


#[derive(Error, Debug, PartialEq, Clone)]
enum ExampleError {
    #[error("error with redis cache `{0}`")]
    RedisError(String),
    #[error("error with disk cache `{0}`")]
    DiskError(String),
}

/// 在`redis`中缓存异步函数的结果。
/// 缓存键将以`cache_redis_prefix`前缀。
/// `map_error`必须指定 `closure` 才能将任何 `redis` 缓存错误转换为函数返回的相同类型的错误。
/// 所有[io_cached]函数都必须返回 [Results]。
///
/// 需要`async` 和 (`redis_tokio` 或 `redis_async_std`) feature
///
/// - map_error：错误映射，将函数中的Err通过指定的闭包映射
/// - ty: 指定缓存类型(需实现[IOCached] 或 [IOCachedAsync] trait)
/// - create: 创建缓存对象
/// - convert： 指定缓存的key
///
/// 说明
/// - redis key 为 cached_redis_prefix + 参数secs的值
/// - 过期时间为 10
#[io_cached(
    map_error = r##"|e| ExampleError::RedisError(format!("{:?}", e))"##,
    ty = "AsyncRedisCache<String, String>",
    create = r##" {
        AsyncRedisCache::new("cached_redis_prefix", 10)
            .set_refresh(true)
            .build()
            .await
            .expect("error building example redis cache")
    } "##,
    convert = r#"{ format!("cache:{}", secs) }"#
)]
async fn async_cached_sleep_secs(secs: u64) -> Result<String, ExampleError> {
    tokio::time::sleep(Duration::from_secs(secs)).await;
    Ok(secs.to_string())
}


/// 将函数的结果缓存在磁盘上。
/// 缓存文件将存储在 system cache dir 下，除非 或 create 参数另有指定disk_dir。
/// map_error必须指定 closure 才能将任何磁盘缓存错误转换为函数返回的相同类型的错误。
/// 所有[io_cached]函数都必须返回 [Results]。
///
/// 需要`disk_store` feature
#[io_cached(
    map_error = r##"|e| ExampleError::DiskError(format!("{:?}", e))"##,
    disk = true
)]
fn cached_sleep_secs(secs: u64) -> Result<String, ExampleError> {
    sleep(Duration::from_secs(secs));
    Ok(secs.to_string())
}